Hosting MongoDB in .NET Aspire

Today, you will learn how to integrate your distributed .NET Aspire application with MongoDB by using Aspire Components. We will walk through an example application that populates a MongoDB instance with some data and retrieves the data inside one of the hosted services.

But before we talk about it, I’ll need to make an announcement. My new book, .NET Aspire Made Easy, is now 50% complete and is available for early access. In fact, today’s conversation is taken out from one of its upcoming chapters.

While the book is in early access, I am keeping its price very low. However, if you buy a copy, you will still get access to all subsequent updates, including its final version once all remaining chapters are added.

Not only this, but you will also get a chance to influence the direction of the book by providing early feedback. If there is any topic that isn’t listed in the ToC that you would like me to cover, I will listen to you.

You can get your copy here.

Now, let’s get back to discussing MongoDB integration with .NET Aspire.

Code Samples

Today’s article contains several code samples. If you want to see how all of those integrate into a complete application, such an application can be found via the following link:

https://github.com/fiodarsazanavets/dotnet-aspire-examples/tree/main/AspireStarterProjectWithMongoDb/AspireApp

I realize that some of you may be new to MongoDB. Therefore, before we start, let’s recap what MongoDB is. If you are already familiar with this database type, you can skip the next section.

Quick Recap of MongoDB

MongoDB is a NoSQL, open-source database designed for scalability, flexibility, and ease of development. It stores data in a flexible, JSON-like format called BSON (Binary JSON), allowing for more complex data structures than traditional relational databases.

Unlike SQL databases, MongoDB doesn’t rely on tables and rows, but instead uses collections and documents, making it ideal for handling unstructured or semi-structured data. However, since it doesn’t consist of tables, MongoDB doesn’t have relationships and foreign key constraints either.

The main disadvantage of MongoDB is that it’s hard to use in scenarios where tracking relationships between different entities is important. On the other hand, it can perform better than an SQL-based relational database in applications that require fast, iterative development, real-time analytics, or the ability to scale easily. Therefore, it’s widely used in e-commerce platforms, social networks, and IoT applications.

Now, let’s see how MongoDB can be hosted inside a .NET Aspire setup.

Hosting a MongoDB Component

To host a MongoDB container in Aspire, we will need to add the following NuGet package to the Aspire host project:

Aspire.Hosting.MongoDB

Then, in the startup code of the host application represented by the Program.cs file, we will need to create a MongoDB server instance and a database reference, as the following example demonstrates:

var mongo = builder.AddMongoDB("mongo");
var mongodb = mongo.AddDatabase("mongodb");

We can then add the database reference to any service that we want to connect to MongoDB by passing the database reference object as a parameter into the WithReference() method executed on that service reference. Here is a reminder of how it can be done:

var apiService = builder
    .AddProject<Projects.AspireApp_ApiService>("apiservice")
    .WithReference(mongodb);

We can apply some additional configuration to the above objects, but these examples are sufficient to launch a MongoDB container inside a distributed Aspire app. Now, let’s look at how we can interact with it from another service.

Working With a MongoDB Client

If we want to interact with data stored in MongoDB from any other service hosted in Aspire, we need to add the following NuGet package reference to this service:

Aspire.MongoDB.Driver

The following code will need to be invoked in the Program.cs file to register all relevant MongoDB dependencies:

builder.AddMongoDBClient("mongodb");

The string value we pass into the AddMongoDBClient() method is the name of the database reference we registered in the host application. The connection string will be configured automatically by Aspire since we are using an Aspire-specific client library.

Then, to represent an individual BSON document stored in a MongoDB database, we would need a class with either an Id or ObjectId property as a string. This property will store a unique identifier for each document. For example, this is what the WeatherForecast class would look like if it were to represent a MongoDB document:

public class WeatherForecast
{
    public string Id { get; set; }

    public DateOnly Date { get; set; }

    public int TemperatureC { get; set; }

    public string? Summary { get; set; }

    [NotMapped]
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

Let’s now see how this class can be used. To do so, we can create the following block inside the Program.cs file:

using (var scope = app.Services.CreateScope())
{
}

Next, we will resolve an implementation of the IMongoClient interface, which we previously registered by invoking the AddMongoDBClient() method. This object will then be used to create a database (if it doesn’t exist) and create a database collection, which is a MongoDB equivalent of a database table. This is how it’s all done:

var mongoClient = scope.ServiceProvider.GetRequiredService<IMongoClient>();

var database = mongoClient.GetDatabase("WeatherDB");
var collection = database.GetCollection<WeatherForecast>("WeatherForecasts");

The next step would be to see if the collection is empty. If it is, we will populate it with the seed data. To check if it’s empty, we count the documents by executing the following code:

var count = collection.CountDocuments(new BsonDocument());

Next, we will populate the collection if the document count is zero:

if (count == 0)
{
    var weatherForecasts = new List<WeatherForecast>();

    foreach (var index in Enumerable.Range(1, 5))
    {
        var date = DateOnly.FromDateTime(DateTime.Now.AddDays(index));
        var temperatureC = Random.Shared.Next(-20, 55);
        var summary = summaries[Random.Shared.Next(summaries.Length)];

        weatherForecasts.Add(new WeatherForecast
        {
            Id = Guid.NewGuid().ToString(),
            Date = date,
            TemperatureC = temperatureC,
            Summary = summary
        });
    }

    collection.InsertMany(weatherForecasts);
}

Finally, we would need to modify the API endpoint so it can retrieve the data from the MongoDB database:

app.MapGet("/weatherforecast", ([FromServices] IMongoClient mongoClient) =>
{
    var database = mongoClient.GetDatabase("WeatherDB");
    var collection = database.GetCollection<WeatherForecast>("WeatherForecasts");

    var weatherForecasts = collection.Find(new BsonDocument()).ToList();

    return weatherForecasts.ToArray();
});

We are using the standard MongoDB C# Driver, so we can do any other operations available with this client library. The Aspire.MongoDB.Driver package just provides an Aspire-specific wrapper for it.

Wrapping Up

MongoDB is not the only NoSQL database we can host as an Aspire component. There are other databases too, such as Cosmos DB and Azure Table Storage. The latter can be hosted in Aspire in an emulated mode without having to connect it to Azure.

I talk about this in the upcoming chapter of my new book, which, as I mentioned previously, is now available for early access.